using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Core
{
    /**
     * This is a first-pass implementation of regular expression support.
     * It is subject to change based on community feedback. 
     * In that sense, use it at your own risk.
     * @version $Id: RegexBuiltins.java,v 1.14 2005/06/01 22:39:07 ddekany Exp $
     * @author Jonathan Revusky
     */
    abstract class RegexBuiltins
    {
        static readonly Dictionary<string, Regex> PatternLookup = new Dictionary<string, Regex>();
        static readonly LinkedList<string> Patterns = new LinkedList<string>();
        const int PatternCacheSize = 100;
        
        static Regex GetPattern(string patternString, string flagString)
        {
            RegexOptions flags = 0;
            string patternKey = patternString + (char) 0 + flagString;
            Regex result = PatternLookup.GetValueOrDefault(patternKey);
            if (result != null)
            {
                return result;
            }
            if (string.IsNullOrEmpty(flagString))
            {
                try
                {
                    result = new Regex(patternString);
                }
                catch (ArgumentException e)
                {
                    throw new TemplateModelException(e);
                }
            }
            else
            {
                if (flagString.IndexOf('i') >= 0)
                {
                    flags = flags | RegexOptions.IgnoreCase;
                }
                if (flagString.IndexOf('m') >= 0)
                {
                    flags = flags | RegexOptions.Multiline;
                }
                if (flagString.IndexOf('c') >= 0)
                {
                    flags = flags | RegexOptions.IgnorePatternWhitespace;
                }
                if (flagString.IndexOf('s') >= 0)
                {
                    //flags = flags | Pattern.DOTALL;
                    throw new NotImplementedException();
                }
                try
                {
                    result = new Regex(patternString, flags);
                }
                catch (ArgumentException e)
                {
                    throw new TemplateModelException(e);
                }
            }
            Patterns.AddLast(patternKey);
            PatternLookup.Add(patternKey, result);
            if (Patterns.Count > PatternCacheSize)
            {
                string first = Patterns.First.Value;
                Patterns.RemoveFirst();
                PatternLookup.Remove(first); // NOTE: Assumed this is what was supposed to be done?
            }
            return result;
        }

        internal class MatchesBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel targetModel = Target.GetAsTemplateModel(env);
                AssertNonNull(targetModel, this, env);
                if (!(targetModel is ITemplateScalarModel))
                {
                    throw InvalidTypeException(targetModel, Target, env, "string");
                }
                return new MatcherBuilder((ITemplateScalarModel) targetModel);
            }
        }

        internal class GroupsBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel targetModel = Target.GetAsTemplateModel(env);
                AssertNonNull(targetModel, this, env);
                if (targetModel is RegexMatchModel)
                {
                    return ((RegexMatchModel) targetModel).GetGroups();
                }
                if (targetModel is RegexMatchModel.MatchImpl)
                {
                    return ((RegexMatchModel.MatchImpl) targetModel).Subs;
                }
                throw InvalidTypeException(targetModel, Target, env, "a regular expression matcher");
            }
        }

        internal class ReplaceBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new ReplaceMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }
        }

        internal class SplitBuiltIn : BuiltIn
        {
            protected override ITemplateModel _getAsTemplateModel(Environment env)
            {
                ITemplateModel model = Target.GetAsTemplateModel(env);
                if (model is ITemplateScalarModel)
                {
                    return new SplitMethod(((ITemplateScalarModel) model).GetAsString());
                }
                throw InvalidTypeException(model, Target, env, "string");
            }
        }
        
        // Represents the match
      
        class RegexMatchModel : ITemplateBooleanModel, ITemplateCollectionModel, ITemplateSequenceModel
        {
            readonly Match _matcher;
            readonly string _input;
            readonly bool _matches;
            ITemplateSequenceModel _groups;
            private List<ITemplateModel> _data;

            internal RegexMatchModel(Match matcher, string input)
            {
                _matcher = matcher;
                _input = input;
                _matches = matcher.Success;
            }
            
            public bool GetAsBoolean()
            {
                return _matches;
            }
            
            public ITemplateModel Get(int i)
            {
                InitSequence();
                return _data[i];
            }
            
            public int Size()
            {
                InitSequence();
                return _data.Count;
            }
            
            private void InitSequence()
            {
                if (_data != null) return;

                _data = new List<ITemplateModel>();
                ITemplateModelIterator it = Iterator();
                while (it.HasNext())
                {
                    _data.Add(it.Next());
                }
            }
            
            public ITemplateModel GetGroups()
            {
                if (_groups == null)
                {
                    _groups = new GroupsImpl(this);
                }
                return _groups;
            }

            private class GroupsImpl : ITemplateSequenceModel
            {
                private readonly RegexMatchModel _regexMatchModel;

                internal GroupsImpl(RegexMatchModel regexMatchModel)
                {
                    _regexMatchModel = regexMatchModel;
                }

                public int Size()
                {
                    try
                    {
                        return _regexMatchModel._matcher.Groups.Count + 1;
                    }
                    catch (Exception e)
                    {
                        throw new TemplateModelException(e);
                    }
                }
                public ITemplateModel Get(int i)
                {
                    try
                    {
                        return new SimpleScalar(_regexMatchModel._matcher.Groups[i].Value);
                    }
                    catch (Exception e)
                    {
                        throw new TemplateModelException(e);
                    }
                }                
            }
            
            public ITemplateModelIterator Iterator()
            {
                //_matcher.reset();
                //return new IteratorImpl(this);
                throw new NotImplementedException();
            }

            private class IteratorImpl : ITemplateModelIterator
            {
                private readonly RegexMatchModel _regexMatchModel;
                private bool _hasFindInfo;

                internal IteratorImpl(RegexMatchModel regexMatchModel)
                {
                    _regexMatchModel = regexMatchModel;
                    _regexMatchModel._matcher.NextMatch();
                    _hasFindInfo = _regexMatchModel._matcher.Success;
                }

                public bool HasNext()
                {
                    return _hasFindInfo;
                }

                public ITemplateModel Next()
                {
                    if (!HasNext()) throw new TemplateModelException("No more matches");
                    var result = new MatchImpl(_regexMatchModel);
                    _regexMatchModel._matcher.NextMatch();
                    _hasFindInfo = _regexMatchModel._matcher.Success;
                    return result;
                }
            }

            internal class MatchImpl : ITemplateScalarModel
            {
                private readonly RegexMatchModel _regexMatchModel;
                readonly string _match;
                internal readonly SimpleSequence Subs = new SimpleSequence();

                internal MatchImpl(RegexMatchModel regexMatchModel)
                {
                    _regexMatchModel = regexMatchModel;
                    _match = _regexMatchModel._input.Substring(_regexMatchModel._matcher.Index, _regexMatchModel._matcher.Length);
                    for (int i = 0; i < _regexMatchModel._matcher.Groups.Count + 1; i++)
                    {
                        Subs.Add(_regexMatchModel._matcher.Groups[i]);
                    }
                }
                public string GetAsString()
                {
                    return _match;
                }
            }
        }
        
        class MatcherBuilder : ITemplateMethodModel
        {
            readonly string _matchString;

            internal MatcherBuilder(ITemplateScalarModel match)
            {
                _matchString = match.GetAsString();
            }
            
            public object Exec(IList<string> args)
            {
                int numArgs = args.Count;
                if (numArgs == 0)
                {
                    throw new TemplateModelException("Expecting at least one argument");
                }
                if (numArgs > 2)
                {
                    throw new TemplateModelException("Expecting at most two arguments");
                }
                string patternString = args[0];
                string flagString = (numArgs > 1) ? args[1] : string.Empty;
                Regex pattern = GetPattern(patternString, flagString);
                Match matcher = pattern.Match(_matchString);
                return new RegexMatchModel(matcher, _matchString);
            }
        }
        
        class ReplaceMethod : ITemplateMethodModel
        {
            private readonly string _s;

            internal ReplaceMethod(string s)
            {
                _s = s;
            }

            public object Exec(IList<string> args)
            {
                int numArgs = args.Count;
                if (numArgs < 2 || numArgs > 3)
                {
                    throw new TemplateModelException(
                            "?replace(...) needs 2 or 3 arguments.");
                }
                string first = args[0];
                string second = args[1];
                string flags = numArgs > 2 ? args[2] : string.Empty;
                bool caseInsensitive = flags.IndexOf('i') >=0;
                bool useRegexp = flags.IndexOf('r') >=0;
                bool firstOnly = flags.IndexOf('f') >=0;
                string result;
                if (!useRegexp)
                {
                    result = StringUtil.Replace(_s, first, second, caseInsensitive, firstOnly);
                }
                else
                {
                    Regex pattern = GetPattern(first, flags);
                    Match matcher = pattern.Match(_s);
                    //result = firstOnly ? matcher.replaceFirst(second) : matcher.replaceAll(second);
                    throw new NotImplementedException();
                } 
                return new SimpleScalar(result);
            }
        }
        
        class SplitMethod : ITemplateMethodModel
        {
            private readonly string _s;

            internal SplitMethod(string s)
            {
                _s = s;
            }

            public object Exec(IList<string> args)
            {
                int numArgs = args.Count;
                if (numArgs < 1 || numArgs >2 )
                {
                    throw new TemplateModelException(
                            "?replace(...) needs 1 or 2 arguments.");
                }
                string splitString = args[0];
                string flags = numArgs >1 ? args[1] : string.Empty;
                bool caseInsensitive = flags.IndexOf('i') >=0;
                bool useRegexp = flags.IndexOf('r') >=0;
                string[] result;
                if (!useRegexp)
                {
                    result = StringUtil.Split(_s, splitString, caseInsensitive);
                }
                else
                {
                    Regex pattern = GetPattern(splitString, flags);
                    result = pattern.Split(_s);
                }
                return ObjectWrapper.DefaultWrapper.Wrap(result);
            }
        }
    }
}